/*
* ============================================================================
*  Name        : sea_manager.cpp
*  Part of     : Finite state machine framework 
*  Description : 
*  Version     : %version: %
*  
*
* ============================================================================
*/
#include "sea_engine.h"
#include "sea_manager.h"
#include "sea_parser.h"

/**
 * @file sea_manager.cpp
 *
 * @ingroup StateMachineFramework
 */
using namespace sea;

// -------------------------------------------------------------------------------------------------
// Ctor
// -------------------------------------------------------------------------------------------------
Manager::Manager( ):
    m_thread_handle( 0 ),
    m_is_parser( false ),
    m_dispather_host_callback( NULL ),
    m_dispather_host_param( NULL )
{    
}

// -------------------------------------------------------------------------------------------------
// Dtor
// -------------------------------------------------------------------------------------------------
Manager::~Manager( )
{
}

// -------------------------------------------------------------------------------------------------
// Forse manager to exit
// -------------------------------------------------------------------------------------------------
void Manager::destroy( )
{
    SystemStateMachine* system = NULL;
    Object* manager = dynamic_cast< Object* >( this );
    
    for( VIterator< StateMachine* > ism( manager, ManagerStateMachine ); *ism; ism++ )
    {
        if ( system == NULL )
        {
            system = StateMachine::system( );
        }
        system->exit( *ism );
    }
}

// -------------------------------------------------------------------------------------------------
// Wait manager to finish
// -------------------------------------------------------------------------------------------------
Error Manager::wait( )
{
    // Cannot wait on the signal if the call is made 
    // from the context of the same thread
    if ( get_thread_handle( ) == 0 || get_thread_handle( ) == OsThread::currentThreadId( ) )
    {
        return eFailed;
    }

    // Thread wait API
    OsThread::wait( );

    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Clean event queue because of possible Sync events
// -------------------------------------------------------------------------------------------------
void Manager::clean_event_queues( )
{
    OsMutex::Scoped scoped( &m_lock );

    sea::ref< Event > the_event;
    m_system_events.clear( );

    while( ( the_event = m_interal_events.pop( ).get( ) ) != NULL )
    {
        the_event->signal( );
    }

    while( ( the_event = m_external_events.pop( ).get( ) ) != NULL )
    {
        the_event->signal( );
    }
    // There is a change of getting Int/Ext events still in the pipe
    // while Ev_Sys_Trminate is received. 
    // Wipe out events left overs
    m_interal_events.clear( );
    m_external_events.clear( );
}

// -------------------------------------------------------------------------------------------------
// Manager's dispatcher. Using this API allows integration of the 
// state machine framework into existing event handling systems, like Qt
// event loop
// -------------------------------------------------------------------------------------------------
Error Manager::dispatch( bool process_all_events /* = true */ )
{
    // Decouple exec( ) and dispatch( ) APIs
    OsMutex::Scoped protect( &m_dispacher_lock );

    // Store thread Id to detect illegal API calls
    if ( !m_thread_handle )
    {
        m_thread_handle = OsThread::currentThreadId( );
    }

    sea::ref< Event > the_event;

    // Process ALL events
    do
    {
        // In priority order check content of event queues
        if ( !( the_event = m_system_events.pop( ).get( ) ) )
        {
            if ( !( the_event = m_interal_events.pop( ).get( ) ) )
            {
                if ( !( the_event = m_external_events.pop( ).get( ) ) ) 
                {
                    OsMutex::Scoped scoped( &m_lock );
                    if ( get_link( ManagerStateMachine ).get( ) )
                    {
                        break;
                    }
                    else
                    {
                        clean_event_queues( );
                        return eStateMachineNotActive;
                    }
                }
            }
        }

        if ( !handle_event( the_event.get( ) ) ) 
        {
            OsMutex::Scoped scoped( &m_lock );

            // Is there any state machine left?
            if ( get_link( ManagerStateMachine ) == NULL )
            {
                clean_event_queues( );
                return eStateMachineNotActive;
            }
        }
    } while( process_all_events );

    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Add state machine to the manager to dispatch
// -------------------------------------------------------------------------------------------------
Error Manager::join( StateMachine* state_machine, StateMachine* parent /* = NULL */ )
{
    if ( !state_machine )
    {
        return eWrongParameter;
    }

    if ( state_machine->is_alive( ) )
    {
        return eStateMachineIsAlreadyActive;
    }

    {
        OsMutex::Scoped safeE( Engine::engine( )->lock( ) );
        OsMutex::Scoped safe( &m_lock );

        sea::ref< StateMachine > sm( get_right_obj( ManagerStateMachine ).as< StateMachine >( ) );
        if ( sm == NULL )
        {
            return eManagerDoesNotHaveStateMachines;
        }

        // Inhearit exec mode
        state_machine->set_exec_mode( sm->exec_mode( ) );

        // Assign manager
        state_machine->m_manager = this;

        // Before triggering 1st event
        // make sure the state machine is alive
        state_machine->set_alive( true );

        // Create state machine <-> manager relationship
        state_machine->attach_right_of( this, ManagerStateMachine );

        // There may be a parent of the state machine
        if ( parent )
        {
            // Create parent <-> child relationship
            OsMutex::Scoped safe( parent->manager( )->lock( ) );
            state_machine->attach_right_of( parent, ParentChild );
            state_machine->m_parent = parent;
        }
    }

    // Set initial state by sending system message...
    return state_machine->push_event( state_machine->system( )->Ev_Sys_Create( ) );
}

// -------------------------------------------------------------------------------------------------
// Manager's exec method. All state machines under the manager control run in the 
// context of this thread
// -------------------------------------------------------------------------------------------------
void Manager::exec( )
{
    {
        // Decouple exec( ) and dispatch( ) APIs
        OsMutex::Scoped protect( &m_dispacher_lock );

#if defined( SEA_LOG )
        SEA_TRACE( "=== Manager === %x started", this );
#endif

        // Store thread Id to detect illegal API calls
        if ( !m_thread_handle )
        {
            m_thread_handle = OsThread::currentThreadId( );
        }

        // Rock'n roll
        for( ;; ) 
        {
            // Do not wait for parser, as parser generates internal events for 
            // itself
            if ( !m_is_parser )
            {
                // Wait for ANY event 
                m_event_queue_signal.wait( );
            }

            sea::ref< Event > the_event;

            // In priority order check content of event queues
            if ( !( the_event = m_system_events.pop( ).get( ) ) )
            {
                if ( !( the_event = m_interal_events.pop( ).get( ) ) )
                {
                    if ( !( the_event = m_external_events.pop( ).get( ) ) )
                    {
                        if ( !m_is_parser )
                        {
                            // Shouldnt be here, unless state machine itself 
                            // called dispatch( ) API
                            // ASSERT_DEBUG( 0 );
                        }
                        else
                        {
                            continue;
                        }
                    }
                }
            }

            if ( !handle_event( the_event.get( ) ) ) 
            {
                OsMutex::Scoped scoped( &m_lock );

                // Is there any state machine left?
                if ( get_link( ManagerStateMachine ) == NULL )
                {
                    break;
                }
            }
        }
    }
    
    // Claim extra reference
    sea::ref< Manager > manager = this;

    // Remove the manager from Engine
    Engine::engine( )->remove_manager( this );

    clean_event_queues( );
}

// -------------------------------------------------------------------------------------------------
// Invoke action functions associated with the event in the current state. If event
// is not consumed it is passed to the EXCEPTIONS state.
// -------------------------------------------------------------------------------------------------
#if defined( SEA_OS_WIN32 )
    #pragma warning( push )
    #pragma warning( disable : 4189 )
#endif

SEA_INLINE bool Manager::handle_event( Event* event )
{
    // With the dispather API it is possible to get here with NULL event
    if ( !event )
    {
        return false;
    }

    StateMachine* statemachine = event->get_state_machine( );
    SystemStateMachine* system = StateMachine::system( );

    ASSERT_DEBUG( statemachine );

    i::Index nextState = NEXT_STATE_NOJUMP;

    // Ev_Sys_Destroy is triggered by exit( ) as a last accepted event
    if ( event->get_event_id( ) == system->Ev_Sys_Destroy( ) ) 
    {
        statemachine->destroy( );
        return false;
    }

    // Ignore all events but Ev_Sys_Destroy if state machine is not alive
    {
        OsMutex::Scoped scoped( &m_lock );
        if ( !statemachine->is_alive( ) ) 
        {
            // If event is SYNC, signal the whoever is waiting
            // Signal happens even if the event was not processed/ignored
            // by the state machine
            event->signal( );
            return true;
        }
    }

    EventId eventId = event->get_event_id( );

    if ( eventId & StateMachine::sysEventMask )
    {
        if ( eventId == system->Ev_Sys_OnEntry( ) ||
             eventId == system->Ev_Sys_Heartbeat( ) ||
             eventId == system->Ev_Sys_LastHeartbeat( ) )
        {
            // Filter events above if generated in previous state
            if ( event->get_stamp( ) != statemachine->get_state_stamp( ) )
            {
                return true;
            }
        }
        else
        if ( eventId == system->Ev_Sys_Create( ) )
        {
            ASSERT_DEBUG( statemachine->exec_mode( ) != StateMachine::RunDefault );
            if ( statemachine->exec_mode( ) == StateMachine::RunAsync || 
                 statemachine->exec_mode( ) == StateMachine::RunAsyncBatch )
            {
                // SEA_TRACE( " Manager started thread id:%x", 
                //    statemachine->manager( )->id( ) );

                // Signal start of the state machine
                // StateMachine::start( ) should consume this event
                statemachine->signal( );
            }

            statemachine->goto_next_state( statemachine->get_body_field( Parser::eInitialStateIndex ), NULL );
            return true;
        }
    }

    // Check if event is registered with the current or EXCEPTIONS states
    if ( !invoke_action( statemachine, event, statemachine->get_state( ), nextState ) ) 
    {
        // Check if the state machine has only exception state
        // Avoid calling invokeAction twice 
        if ( statemachine->get_state( ) != Parser::eExceptionStateIndex )
        {
#if defined( SEA_LOG )
            // SEA_TRACE( "\n==> EXCEPTION state:\n" );
            if ( !invoke_action( statemachine, event, Parser::eExceptionStateIndex, nextState ) )
            {
                log_unrecognized_event( statemachine, eventId );
            }
#else
            invoke_action( statemachine, event, Parser::eExceptionStateIndex, nextState );
#endif
        }
        else
        {
#if defined( SEA_LOG )
            log_unrecognized_event( statemachine, event->get_event_id( ) );
#endif
        }
    }
    
    // If event is SYNC, signal the whoever is waiting
    // Signal happens even if the event was not processed/ignored
    // by the state machine
    event->signal( );

    // NEXT_STATE_COUNT state processed in gotoNextState
    if ( nextState != NEXT_STATE_NOJUMP )
    {
        statemachine->goto_next_state( nextState, event );
    }
    return true;
}

#if defined( SEA_OS_WIN32 )
    #pragma warning( pop )
#endif

// -------------------------------------------------------------------------------------------------
// Call action functions associated with the event in a state. 
// -------------------------------------------------------------------------------------------------
SEA_INLINE bool Manager::invoke_action( StateMachine* state_machine, Event* event, i::Offset state_offset, i::Offset& next )
{
    // Check if event is registered with the current state
    register bool processed = false;
    register int offset = state_offset + Parser::eNumberOfRecords;

    // Number of event records
    register int nor = state_machine->get_body_field( offset );

    // Advance to the first event record
    offset = state_offset + Parser::eEventRecords;

    MetaSystemRegistryEntry* registry = state_machine->registry( );

    vector< MetaAction >& actions = registry->m_actions;
    vector< MetaAction >& sysActions = StateMachine::system( )->registry( )->m_actions;

    ASSERT_DEBUG( sysActions.size( ) );

    // Prepare parameter for action function(s)
    state_machine->m_current_event = event;
    void* param = event ? event->as< void >( ) : NULL;

    // Call actions associated with the events
    for ( int i = 0; i < nor; ++i )
    {
        if ( state_machine->get_body_field( offset + Parser::eEvent ) == event->get_event_id( ) )
        {
            int action_index = state_machine->get_body_field( offset + Parser::eAction );
#if defined( SEA_LOG )
            log_event( state_machine, offset );
#endif
            if ( action_index != NULL_ACTION )
            {
                if ( action_index & StateMachine::sysEventMask )
                {
                    action_index &= ~StateMachine::sysEventMask;

                    // System actions get current state machine as a parameter
                    ( ( StateMachine::system( ) )->*( sysActions[ action_index ] ).get_function( ) )( state_machine );
                }
                else
                {
                    ( ( state_machine )->*( actions[ action_index ] ).get_function( ) )( param );
                }
            }

            next = state_machine->get_body_field( offset + Parser::eNextState );
            processed = true;
        } 
        else
        {
            if ( processed )
            {
                state_machine->m_current_event = NULL;
                return true;
            }
        }
        // Advance to the next event record
        offset += 3;
    }
    state_machine->m_current_event = NULL;
    return processed;
}

#if defined( SEA_LOG )

// -------------------------------------------------------------------------------------------------
// Log state's event record
// -------------------------------------------------------------------------------------------------
void Manager::log_event( StateMachine* state_machine, i::Offset event_record_offset )
{
    if ( !state_machine->m_logging )
    {
        return;
    }

    MetaSystemRegistryEntry* registry = state_machine->registry( );

    int eventId = state_machine->get_body_field( event_record_offset + Parser::eEvent );
    vector< MetaEvent >* eventArray;

    if ( eventId & StateMachine::sysEventMask )
    {
        MetaSystemRegistryEntry* system = StateMachine::system( )->registry( );
        eventArray = &system->m_events;
        eventId &= ~StateMachine::sysEventMask;
    }
    else
    if ( eventId & StateMachine::intEventMask )
    {
        eventId &= ~StateMachine::intEventMask;
        eventArray = &registry->m_events;
    }
    else
    {
        eventArray = &registry->m_events;
    }
    MetaEvent metaEvent = ( *eventArray )[ eventId ];

    int action_index = state_machine->get_body_field( event_record_offset + Parser::eAction );
    String actionName;
    if ( action_index == NULL_ACTION )
    {
        actionName = "null";
    }
    else
    if ( action_index & StateMachine::sysEventMask )
    {
        MetaSystemRegistryEntry* system = StateMachine::system( )->registry( );
        action_index &= ~StateMachine::sysEventMask;
        actionName = system->m_actions[ action_index ].name( );
    }
    else
    {
        actionName = registry->m_actions[ action_index ].name( );
    }

    int nextState = state_machine->get_body_field( event_record_offset  + Parser::eNextState );
    String nextStateName;

    if ( nextState == NEXT_STATE_NOJUMP )
    {
        nextStateName = "nojump";
    }
    else
    if ( nextState == NEXT_STATE_COUNT )
    {
        nextStateName = "next";
    }
    else
    {
        nextStateName = registry->m_offset2name[ nextState ];
    }

    state_machine->m_current_event_name = metaEvent.name( );

    if ( nextStateName.empty( ) )
    {
        SEA_TRACE( "[%08x] %s::%-25s %-25s %-5d", 
            state_machine, 
            registry->m_name.c_str( ),
            metaEvent.name( ), 
            actionName.c_str( ), 
            registry->m_offset2index[ nextState ] );
    }
    else
    {
        SEA_TRACE( "[%08x] %s::%-25s %-25s %-25s", 
            state_machine,
            registry->m_name.c_str( ),
            metaEvent.name( ), 
            actionName.c_str( ), 
            nextStateName.c_str( ),
            registry->m_name.c_str( ) );
    }
}

// -------------------------------------------------------------------------------------------------
// Log state's transition
// -------------------------------------------------------------------------------------------------
void Manager::log_state_transition( StateMachine* state_machine, i::Offset offset )
{
    if ( !state_machine->m_logging )
    {
        return;
    }

    MetaSystemRegistryEntry* registry = state_machine->registry( );

    SEA_TRACE( "\n" );

    String nextStateName = registry->m_offset2name[ offset ];
    String currentStateName = registry->m_offset2name[ state_machine->get_state( ) ];

    if ( !state_machine->get_state( ) )
    {
        if ( nextStateName.empty( ) )
        {
            SEA_TRACE( "[%08x] lanching %s:: +++> %d", state_machine, registry->m_name.c_str( ), registry->m_offset2index[ offset ] );
        }
        else
        {
            SEA_TRACE( "[%08x] lanching %s:: +++> %s", state_machine, registry->m_name.c_str( ), nextStateName.c_str( ) );
        }
    }
    else
    {
        if ( nextStateName.empty( ) )
        {
            SEA_TRACE( "[%08x] %s::%d -> %d", 
                state_machine,
                registry->m_name.c_str( ),
                registry->m_offset2index[ state_machine->get_state( ) ],
                registry->m_offset2index[ offset ] );
        }
        else
        {
            SEA_TRACE( "[%08x] %s::%s -> %s", 
                state_machine,
                registry->m_name.c_str( ),
                currentStateName.c_str( ),
                nextStateName.c_str( ) );
        }
    }
    state_machine->m_current_state_name = nextStateName;
}

// -------------------------------------------------------------------------------------------------
// Log unrecognized event
// -------------------------------------------------------------------------------------------------
void Manager::log_unrecognized_event( StateMachine* state_machine, i::Index event_id )
{
    if ( !state_machine->m_logging )
    {
        return;
    }

    MetaSystemRegistryEntry* registry = state_machine->registry( );

    vector< MetaEvent >* eventArray;

    if ( event_id & StateMachine::sysEventMask )
    {
        MetaSystemRegistryEntry* system = StateMachine::system( )->registry( );
        eventArray = &system->m_events;

        if ( event_id == StateMachine::system( )->Ev_Sys_OnEntry( ) ||
             event_id == StateMachine::system( )->Ev_Sys_Heartbeat( ) ||
             event_id == StateMachine::system( )->Ev_Sys_LastHeartbeat( ) )
        {
            // Those events are often not processed 
            return;
        }

        if ( event_id == StateMachine::system( )->Ev_Sys_OnEntry( ) )
        {
            // Those events are often not processed 
            return;
        }
        event_id &= ~StateMachine::sysEventMask;
    }
    else
    if ( event_id & StateMachine::intEventMask )
    {
        event_id &= ~StateMachine::intEventMask;
        eventArray = &registry->m_events;
    }
    else
    {
        eventArray = &registry->m_events;
    }

    ASSERT_DEBUG( ( size_t )event_id < eventArray->size( ) );

    MetaEvent metaEvent = ( *eventArray )[ event_id ];
    String currentStateName = registry->m_offset2name[ state_machine->get_state( ) ];

    if ( currentStateName.empty( ) )
    {
        SEA_TRACE( "\nxxxxxx IGNORED EVENT:\n[%08x] %s::%s in STATE: %d\n", 
            state_machine,
            registry->m_name.c_str( ),
            metaEvent.name( ), 
            registry->m_offset2index[ state_machine->get_state( ) ] );
    }
    else
    {
        SEA_TRACE( "\nxxxxxx IGNORED EVENT:\n[%08x] %s::%s in STATE: %s\n", 
            state_machine,
            registry->m_name.c_str( ),
            metaEvent.name( ), 
            currentStateName.c_str( ) );
    }
}

#endif

// -------------------------------------------------------------------------------------------------
